package business;

import java.util.ArrayList;
import java.util.List;

import node.AAspect;
import node.PAspect;
import util.Globals;
import util.IntertypeType;
import util.LogicalOperators;
import exception.AdviceHasntPointcutAssociatedOnAspectException;
import exception.AspectIdentifierAlreadyExistsException;
import exception.FunctionDeclarationIntertypeAlreadyExistsOnAspectException;
import exception.NoAspectFoundException;
import exception.VarDeclarationIntertypeAlreadyExistsOnAspectException;

public class AspectFile {
	private String nameOfPropertyAssociated;
	private List<Aspect> aspectListToManipulate;
	private List<String> aspectDeclaretedNames; 
	private List<PAspect> aspectListImportedFromFileInput;

	public AspectFile(List<PAspect> aspectListImportedFromFileInput){
		this.aspectDeclaretedNames = new ArrayList<String>();
		this.aspectListToManipulate = new ArrayList<Aspect>();
		this.aspectListImportedFromFileInput = aspectListImportedFromFileInput;
	}

	/* BEGIN ASPECT ACTIONS */
	/**	
	 *  Check if the aspect file imported is valid:
	 *  - Verify if exists aspect;
	 *  - Verify if exists other aspect with the same identifier;
	 *  
	 *  Creates a aspect list to manipulate.
	 *  
	 * @throws FunctionDeclarationIntertypeAlreadyExistsOnAspectException 
	 * @throws AdviceHasntPointcutAssociatedOnAspectException 
	 * @throws VarDeclarationIntertypeAlreadyExistsOnAspectException 
	 */
	public void validate() throws NoAspectFoundException, AspectIdentifierAlreadyExistsException, FunctionDeclarationIntertypeAlreadyExistsOnAspectException, VarDeclarationIntertypeAlreadyExistsOnAspectException, AdviceHasntPointcutAssociatedOnAspectException{
		if(aspectListImportedFromFileInput != null && !aspectListImportedFromFileInput.isEmpty()){
			fillDeclaratedNames();
			fillAspectListToManipulate();
		}else{
			throw new NoAspectFoundException();
		}
	}

	/**
	 * Reads the aspect file and gets all aspects name to populate a list.
	 * 
	 * @throws AspectIdentifierAlreadyExistsException
	 */
	private void fillDeclaratedNames() throws AspectIdentifierAlreadyExistsException{
		for(PAspect aspectInPositionList : aspectListImportedFromFileInput){
			String aspectIdentifier = ((AAspect)aspectInPositionList).getIdentifier().toString();
			if(!nameAlreadyExists(aspectIdentifier)){
				/* Puts the aspects name on list. */
				aspectDeclaretedNames.add(aspectIdentifier);
			}else{
				throw new AspectIdentifierAlreadyExistsException(aspectIdentifier);
			}
		}
	}

	/**
	 * Populates a aspect to manipulate list with the components extracted from the imported file.
	 * 
	 * @throws FunctionDeclarationIntertypeAlreadyExistsOnAspectException
	 * @throws VarDeclarationIntertypeAlreadyExistsOnAspectException
	 * @throws AdviceHasntPointcutAssociatedOnAspectException
	 */
	private void fillAspectListToManipulate() throws FunctionDeclarationIntertypeAlreadyExistsOnAspectException, VarDeclarationIntertypeAlreadyExistsOnAspectException, AdviceHasntPointcutAssociatedOnAspectException{
		for(PAspect aspectInPositionList : aspectListImportedFromFileInput){
			AAspect aspectRetriverFromInputFile = (AAspect) aspectInPositionList;
			Aspect aspectToManipulate = new Aspect(aspectRetriverFromInputFile.getIdentifier().toString(),aspectRetriverFromInputFile.getComponentAspect());
			aspectListToManipulate.add(aspectToManipulate);
			
//			TImplementsProperty property = (TImplementsProperty) aspectRetriverFromInputFile.getImplementsProperty();
//			nameOfPropertyAssociated = property.getText();
		}
	}

	/**
	 * Checks if the aspect name already exists on aspect name list.
	 * 
	 * @param nameToFind
	 * @return
	 */
	private boolean nameAlreadyExists(String nameToFind){
		boolean existsName = false;
		if(aspectDeclaretedNames!=null && !aspectDeclaretedNames.isEmpty()){
			for(String actualName : aspectDeclaretedNames){
				/* If exists the id declareted, returns true. */
				if(nameToFind.equalsIgnoreCase(actualName)){
					existsName = true;
					break;
				}
			}
		}
		return existsName;
	}
	
	/**
	 * Verify on netlogo code, if the aspect component imported make affect.
	 * If not, they will be added on componentAspectNoAffectsNetlogoCodeList.
	 * 
	 * @param netlogoCode
	 * @return
	 */
	public AspectComponents checkAspectComponentsAtuationInNetLogoCode(NetlogoCode netlogoCode){
		List<Pointcut> noAffectPointcutList = checkPointcutAtuation(netlogoCode);
		List<Intertype> noAffectIntertypeList = checkIntertypeAtuation(netlogoCode);
		List<AdvicePointcut> noAffectAdvicePointcutList = checkAdvicesPointcutAtuation(netlogoCode);

		return new AspectComponents(null, noAffectPointcutList, noAffectIntertypeList, noAffectAdvicePointcutList);
	}
	/* END ASPECT ACTIONS */

	/* BEGIN POINTCUT VALIDATE */ 
	/**
	 * For each pointcut, checks if it affect on netlogo code.
	 * If not puts it on no affect netlogo code list.
	 * 
	 * @param netlogoCode
	 * @return
	 */
	private List<Pointcut> checkPointcutAtuation(NetlogoCode netlogoCode){
		List<Pointcut> noAffectPointcutList = new ArrayList<Pointcut>();

		for(Aspect aspect : aspectListToManipulate){
			for(Pointcut pointcut : aspect.getAspectComponents().getPointcutList()){
				Boolean existsFunctionInCode = false; 
				
				/* Check validate on code by function id or target code. */
				if(pointcut.getFunctionIdentifier()!=null && !pointcut.getFunctionIdentifier().equals(""))
					existsFunctionInCode = existsPointcutFunctionRefering(pointcut.getFunctionIdentifier(), pointcut.getLogicalOperator(), pointcut.getLinesToAct(), netlogoCode.getNetlogoLinesFunctionNames(), netlogoCode.getCodeLines());
				
				if(pointcut.getLinesToAct()!=null && !pointcut.getLinesToAct().equals("")){
					List<CodePosition> listOfCodeFound =  Globals.findTargetCodeReferenced(netlogoCode.getCodeLines(), pointcut.getLinesToAct());
					if(!listOfCodeFound.isEmpty())
						existsFunctionInCode = true;
				}
					
				/* If the pointcut refering a code part, its valid. */
				pointcut.setInNetlogoCode(existsFunctionInCode);
				
				/* If the pointcut dont make affect, puts it on the noAffect list. */
				if(!existsFunctionInCode){
					noAffectPointcutList.add(pointcut);
				}
			}
		}

		return noAffectPointcutList;
	}

	/**
	 * Checks if exists the function refering by pointcut on netlogo code.
	 * 
	 * @param nameFunction
	 * @param codeLines
	 * @return
	 */
	private boolean existsPointcutFunctionRefering(String nameFunction, LogicalOperators logicalOperator, String targetCode, List<Line> functionLines, List<Line> codeLines){
		
		boolean hasNameFunction = nameFunction != null;
		boolean hasPointcutDesignator = targetCode != null;
		
		boolean nameFunctionAffectCode = false;
		boolean pointcutDesignatorAffectCode = false; 
		
		/* Checks if the name function is in netlogo code.*/
		if(hasNameFunction){
			List<String> functionIdList = Globals.extractIdFuntionsFromFunctionNamesLinesList(functionLines);

			nameFunctionAffectCode = Globals.verifyFunctionIdExistsOnIdFunctionList(functionIdList, nameFunction);
			
			if(logicalOperator == null)
				return nameFunctionAffectCode;
		}
		
		/* Checks if the target code is in netlogo code.*/
		if(hasPointcutDesignator){
			pointcutDesignatorAffectCode = Globals.verifyTargetCodeIsOnNetLogoCode(codeLines, targetCode);
			
			if(logicalOperator == null)
				return pointcutDesignatorAffectCode;
		}
		
		boolean isAndLogicalOperator = logicalOperator.getValue().equals(Globals.POINTCUT_AND_LOGICAL_OPERATOR) ? true : false;
		
		/* Matches the logical operator with the target code and name function. */
		if(isAndLogicalOperator){
			return hasNameFunction && pointcutDesignatorAffectCode;
		}else{
			return hasNameFunction || pointcutDesignatorAffectCode;
		}
			
	}
	/* END POINTCUT VALIDATE */ 

	/* BEGIN ADVICEPOINTCUT VALIDATE */ 
	/**
	 * For each advicepointcut, checks if it affect on netlogo code.
	 * If not puts it on no affect netlogo code list.
	 * 
	 * @param netlogoCode
	 * @return
	 */
	private List<AdvicePointcut> checkAdvicesPointcutAtuation(NetlogoCode netlogoCode){
		List<AdvicePointcut> noAffectAdvicePointcutList = new ArrayList<AdvicePointcut>();

		for(Aspect aspect : aspectListToManipulate){
			for(AdvicePointcut advicePointcut : aspect.getAspectComponents().getAdvicePointcutList()){
				boolean existsFunctionInCode = existsPointcutFunctionRefering(advicePointcut.getFunctionIdentifier(), advicePointcut.getLogicalOperator(), advicePointcut.getLinesToAct(), netlogoCode.getNetlogoLinesFunctionNames(), netlogoCode.getCodeLines());

				/* If the pointcut refering a code part, its valid. */
				advicePointcut.setInNetlogoCode(existsFunctionInCode);
				
				/* If the advice pointcut dont make affect, puts it on the noAffect list. */
				if(!existsFunctionInCode){
					noAffectAdvicePointcutList.add(advicePointcut);
				}
			}
		}
		
		return noAffectAdvicePointcutList;
	}
	/* END ADVICEPOINTCUT VALIDATE */ 

	/* BEGIN INTERTYPE VALIDATE */
	/**
	 * For each intertype, checks if it affect on netlogo code.
	 * If not puts it on no affect netlogo code list.
	 * 
	 * @param netlogoCode
	 * @return
	 */
	private List<Intertype> checkIntertypeAtuation(NetlogoCode netlogoCode){
		boolean isIntertypeInCode = false;
		List<Intertype> noAffectIntertypeList = new ArrayList<Intertype>();

		for(Aspect aspect : aspectListToManipulate){
			for(Intertype intertype : aspect.getAspectComponents().getIntertypeList()){
				String id = null;
				
				/* Check the intertype type: */
				if(intertype.getType() == IntertypeType.FUNCTION){
					/* Retrive the function id. */
					id = Globals.retriveIdFromBodyFunction(intertype.getCodeToInclude());
					
				}else if (intertype.getType() == IntertypeType.VAR || intertype.getType() == IntertypeType.GLOBAL){
					/* Gets var id from intertype. */
					String varId = Globals.extractIdFromCodeToInclude(intertype);
				}
				
				if(id != null){
					/* Checks if this id already exists on netlogo code. */
					isIntertypeInCode = Globals.existsIdOnNetlogoCode(id, netlogoCode.getCodeLines());
					
					/* Sets if intertype is valid include. */
					intertype.setValidInclude( !isIntertypeInCode );
					
					/* if this id already exists puts it on no affect code list. */
					if(isIntertypeInCode){
						noAffectIntertypeList.add(intertype);
					}
				}
			}
		}
		return noAffectIntertypeList;
	}

	/* END INTERTYPE VALIDATE */

	public List<Aspect> getAspectListToManipulate() {
		return aspectListToManipulate;
	}

	public void setAspectListToManipulate(List<Aspect> aspectListToManipulate) {
		this.aspectListToManipulate = aspectListToManipulate;
	}

	public List<String> getAspectDeclaretedNames() {
		return aspectDeclaretedNames;
	}

	public void setAspectDeclaretedNames(List<String> aspectDeclaretedNames) {
		this.aspectDeclaretedNames = aspectDeclaretedNames;
	}

	public List<PAspect> getAspectListImportedFromFileInput() {
		return aspectListImportedFromFileInput;
	}

	public void setAspectListImportedFromFileInput(
			List<PAspect> aspectListImportedFromFileInput) {
		this.aspectListImportedFromFileInput = aspectListImportedFromFileInput;
	}

	public String getNameOfPropertyAssociated() {
		return nameOfPropertyAssociated;
	}

	public void setNameOfPropertyAssociated(String nameOfPropertyAssociated) {
		this.nameOfPropertyAssociated = nameOfPropertyAssociated;
	}
	
}
